﻿using System;
using System.Threading;
using System.Windows;

namespace Aspire
{
    /// <summary>
    /// A collection of useful functions for working with UI in Silverlight.
    /// </summary>
    public static class UiUtilities
    {
        private static Thread UiThread { get; set; }
        private static ManualResetEvent initResetter = new ManualResetEvent(false);
        static UiUtilities()
        {
            try
            {
                var visual = Application.Current.RootVisual;
                UiThread = Thread.CurrentThread;
                initResetter.Set();
            }
            catch
            {
                Deployment.Current.Dispatcher.BeginInvoke(() =>
                {
                    UiThread = Thread.CurrentThread;
                    initResetter.Set();
                });
            }
        }

        /// <summary>
        /// Executes a function on the UI thread, blocking until the result has been retrieved.
        /// This method is safe to use whether or not execution is already on the UI thread, since
        /// it only switches threads if necessary.
        /// </summary>
        /// <typeparam name="T">The return type of the function.</typeparam>
        /// <param name="func">The function to execute (usually a lambda).</param>
        /// <returns>The value returned by the function.</returns>
        /// <exception cref="System.Exception">Any exception thrown by the function will be re-thrown by this method
        /// on the initiating thread.</exception>
        public static T ExecuteOnUiThread<T>(Func<T> func)
        {
            if (UiThread == null) initResetter.WaitOne();
            if (Thread.CurrentThread.Equals(UiThread))
            {
                return func();
            }
            else
            {
                T result = default(T);
                object waitLock = new object();
                Exception error = null;
                lock (waitLock)
                {
                    Deployment.Current.Dispatcher.BeginInvoke(() =>
                    {
                        try
                        {
                            result = func();
                        }
                        catch (Exception e)
                        {
                            error = e;
                        }
                        lock (waitLock) Monitor.Pulse(waitLock);
                    });
                    Monitor.Wait(waitLock);
                }
                if (error != null)
                    throw error;
                return result;
            }
        }

        /// <summary>
        /// Executes an action on the UI thread, blocking until the execution has completed.
        /// This method is safe to use whether or not execution is already on the UI thread, since
        /// it only switches threads if necessary.
        /// </summary>
        /// <param name="action">The action to execute (usually a lambda).</param>
        /// <exception cref="System.Exception">Any exception thrown by the action will be re-thrown by this method
        /// on the initiating thread.</exception>
        public static void ExecuteOnUiThread(Action action)
        {
            ExecuteOnUiThread<object>(() =>
            {
                action();
                return null;
            });
        }
    }
}
